/*
Copyright 2010 Iain Surgey.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

vec3	s_alias_forward, s_alias_right, s_alias_up;

float	aliastransform[3][4];
float   aliasworldtransform[3][4];
float 	viewmatrix[3][4];

vec3        RCF_transformed_min, RCF_transformed_max;
vec3		RCF_tmp, RCF_transformed;

affinetridesc_t	r_affinetridesc;

float	s_ziscale;

finalvert_t	finalverts[MAXALIASVERTS + 3];

finalvert_t	*pfv0;
finalvert_t	*pfv1;
finalvert_t	*pfv2;

float lerped_vert[3];
float r_lerp_frontv[3];
float r_lerp_backv[3];

#__body__

/*
================
R_AliasDrawModel
================
*/
private function R_AliasDrawModel ()
{
	/*
	** we have to set our frame pointers and transformations before
	** doing any real work
	*/
	R_AliasSetUpTransform();

	// see if the bounding box lets us trivially reject, also sets
	// trivial accept status
	if ( R_AliasCheckBBox0() == BBOX_TRIVIAL_REJECT )
	{
		return;
	}
	
	//for (i in 0...(RESH*RESV)) Memory.setI32(DRAWOFS1+(i<<2),0);
	alias_visible = true;
	
	// set up the skin and verify it exists
	if ( !R_AliasSetupSkin () )
	{
		Con_Printf( PRINT_ALL, "R_AliasDrawModel : NULL skin found");
		return;
	}
	
	R_AliasSetupFrames();
	R_AliasSetUpLerpData();
	
	/*
	** compute this_frame and old_frame addresses
	*/
	
	s_ziscale = 32768.0 * 65536.0; //(float)0x8000 * (float)0x10000;
	
	R_AliasPreparePoints ();
	
}

/*
=================
R_AliasSetupFrames

=================
*/
private function R_AliasSetupFrames()
{
	#{d| int thisframe}
	
	thisframe = #{s_pmdl->frame};
	
	if (thisframe == 0 || thisframe >= #{s_pmdl->num_frames}) {
		r_thisframe = #{s_pmdl->frames};
		r_lastframe = #{s_pmdl->frames};
		#{s_pmdl->frame = 0};
	} else {
		r_thisframe = #{s_pmdl->frames} + (#{s_pmdl->framesize} * thisframe);
		r_lastframe = r_thisframe - #{s_pmdl->framesize};
	}
}

private function R_AliasSetUpLerpData()
{
	#{d| float	frontlerp, backlerp}
	#{d| int		i}
	
	backlerp = #{s_pmdl->backlerp};
	frontlerp = 1.0 - backlerp;

	/*
	** convert entity's angles into discrete vectors for R, U, and F
	*
	AngleVectors (currententity->angles, vectors[0], vectors[1], vectors[2]);

	/*
	** translation is the vector from last position to this position
	*
	VectorSubtract (currententity->oldorigin, currententity->origin, translation);

	/*
	** move should be the delta back to the previous frame * backlerp
	*
	r_lerp_move[0] =  DotProduct(translation, vectors[0]);	// forward
	r_lerp_move[1] = -DotProduct(translation, vectors[1]);	// left
	r_lerp_move[2] =  DotProduct(translation, vectors[2]);	// up

	VectorAdd( r_lerp_move, r_lastframe->translate, r_lerp_move );

	for (i=0 ; i<3 ; i++)
	{
		r_lerp_move[i] = backlerp*r_lerp_move[i] + frontlerp * r_thisframe->translate[i];
	}*/

	for (i in 0...3)
	{
		#{r_lerp_frontv[i] = frontlerp * r_thisframe->scale[i]};
		#{r_lerp_backv[i]  = backlerp  * r_lastframe->scale[i]};
	}
}

private function R_AliasPreparePoints ()
{
	#{d| int			i}
	#{d| stvert_t	*pstverts}
	#{d| triangle_t	*ptri}
	
	// put work vertexes on stack, cache aligned
	R_AliasTransformFinalVerts( #{s_pmdl->num_xyz}, #{finalverts}, #{r_thisframe->verts}, #{r_lastframe->verts} );
	
// clip and draw all triangles
//
	pstverts = #{s_pmdl->st};
	ptri = #{s_pmdl->tris};
	
	for (i in 0...#{s_pmdl->num_tris})
	{
		#{pfv0 = &finalverts[ptri->index_xyz[0]]};
		#{pfv1 = &finalverts[ptri->index_xyz[1]]};
		#{pfv2 = &finalverts[ptri->index_xyz[2]]};
		
		if ( (#{pfv0->flags} & #{pfv1->flags} & #{pfv2->flags}) != 0 ) {
			ptri+=#{sizeof(triangle_t)};
			continue;		// completely clipped
		}
		
		// insert s/t coordinates
		#{pfv0->s = pstverts[ptri->index_st[0]].s #[<< 16]#};
		#{pfv0->t = pstverts[ptri->index_st[0]].t #[<< 16]#};
		
		#{pfv1->s = pstverts[ptri->index_st[1]].s #[<< 16]#};
		#{pfv1->t = pstverts[ptri->index_st[1]].t #[<< 16]#};
		
		#{pfv2->s = pstverts[ptri->index_st[2]].s #[<< 16]#};
		#{pfv2->t = pstverts[ptri->index_st[2]].t #[<< 16]#};
		
		if ( (#{pfv0->flags} | #{pfv1->flags} | #{pfv2->flags}) == 0 )
		{	// totally unclipped
			atriparms_a = pfv0;
			atriparms_b = pfv1;
			atriparms_c = pfv2;
			R_DrawTriangle();
		}
		else
		{	// partially clipped
			R_AliasClipTriangle (pfv0, pfv1, pfv2);
		}
		
		ptri+=#{sizeof(triangle_t)};
	}
}

private function R_AliasTransformFinalVerts( numpoints : Int, fvr : Int, newv : Int, oldv : Int )
{
	#{s| finalvert_t *fvr}
	#{s| dtrivertx_t *newv}
	#{d| int i}
	#{d| float fscale0, fscale1, fscale2}
	#{d| float bscale0, bscale1, bscale2}
	
	fscale0 = #{r_lerp_frontv[0]};
	fscale1 = #{r_lerp_frontv[1]};
	fscale2 = #{r_lerp_frontv[2]};
	bscale0 = #{r_lerp_backv[0]};
	bscale1 = #{r_lerp_backv[1]};
	bscale2 = #{r_lerp_backv[2]};
	
	for ( i in 0...numpoints )
	{
		
		#{lerped_vert[0] = (#[Memory.getByte(oldv++)]# * bscale0) + (#[Memory.getByte(newv++)]# * fscale0)};
		#{lerped_vert[1] = (#[Memory.getByte(oldv++)]# * bscale1) + (#[Memory.getByte(newv++)]# * fscale1)};
		#{lerped_vert[2] = (#[Memory.getByte(oldv++)]# * bscale2) + (#[Memory.getByte(newv++)]# * fscale2)};
		newv++; oldv++;
		
		#{fvr->xyz[0] = #[DotProduct(]# lerped_vert #[,]# aliastransform[0]) + aliastransform[0][3]};
		#{fvr->xyz[1] = #[DotProduct(]# lerped_vert #[,]# aliastransform[1]) + aliastransform[1][3]};
		#{fvr->xyz[2] = #[DotProduct(]# lerped_vert #[,]# aliastransform[2]) + aliastransform[2][3]};
		
		if ( #{fvr->xyz[2]} < ALIAS_Z_CLIP_PLANE )
		{
			#{fvr->flags = #[ALIAS_Z_CLIP]#};
		}
		else
		{
			#{fvr->flags = 0};
			R_AliasProjectAndClipTestFinalVert( fvr );
		}
		
		fvr += #{sizeof(finalvert_t)};
	}
}

/*
================
R_AliasProjectAndClipTestFinalVert
================
*/
private inline function R_AliasProjectAndClipTestFinalVert( fvr : Int )
{
	#{s| finalvert_t *fvr}
	#{d| float	zi}
	#{d| float	x, y, z}

	// project points
	x = #{fvr->xyz[0]};
	y = #{fvr->xyz[1]};
	z = #{fvr->xyz[2]};
	zi = 1.0 / z;
	
	#{fvr->zi = #[Std.int(]#zi * s_ziscale)};
	
	#{fvr->u = #[Std.int(]#(x * aliasxscale * zi) + aliasxcenter)};
	#{fvr->v = #[Std.int(]#(y * aliasyscale * zi) + aliasycenter)};
	
	if (#{fvr->u} < ord_aliasvrect_x)
		#{fvr->flags = fvr->flags | #[ALIAS_LEFT_CLIP]#};
	if (#{fvr->v} < ord_aliasvrect_y)
		#{fvr->flags = fvr->flags | #[ALIAS_TOP_CLIP]#};
	if (#{fvr->u} > ord_aliasvrectright)
		#{fvr->flags = fvr->flags | #[ALIAS_RIGHT_CLIP]#};
	if (#{fvr->v} > ord_aliasvrectbottom)
		#{fvr->flags = fvr->flags | #[ALIAS_BOTTOM_CLIP]#};	
}

/*
===============
R_AliasSetupSkin
===============
*/
private function R_AliasSetupSkin () : Bool
{
	#{d| int				skinnum}
	#{d| image_t			*pskindesc}
	
	pskindesc = #{s_pmdl->skin};
	if ( pskindesc < 4 )
		return false;
	
	#{r_affinetridesc.pskin = pskindesc->pixels[0]};
	#{r_affinetridesc.skinwidth = pskindesc->width};
	#{r_affinetridesc.skinheight = pskindesc->height};
	
	R_PolysetUpdateTables ();		// FIXME: precalc edge lookups
	
	return true;
}


private inline function R_AliasCheckBBox0 () : Int
{
	#{d| int ccode}

	ccode = R_AliasCheckFrameBBox0();
	
	if ( ccode == BBOX_TRIVIAL_ACCEPT )
		return BBOX_TRIVIAL_ACCEPT;
	else if ( (ccode & BBOX_TRIVIAL_REJECT) != 0 )
		return BBOX_TRIVIAL_REJECT;
	else
		return ( ccode & ~BBOX_TRIVIAL_REJECT );
}


/*
** R_AliasCheckFrameBBox
**
** Checks a specific alias frame bounding box
*/
private function R_AliasCheckFrameBBox0( ) : Int
{
	#{d| int aggregate_and_clipcode, aggregate_or_clipcode}
	aggregate_and_clipcode = ~0;
	aggregate_or_clipcode = 0;
	#{d| int           i, j, clipcode}
	var zclipped : Bool = false;
	var zfullyclipped : Bool = true;
	#{d| float minz, dp}
	minz = 9999.0;
	
	for (i in 0...3)
	{
		#{s_pmdl->mins[i] = r_thisframe->translate[i]};
		#{s_pmdl->maxs[i] = s_pmdl->mins[i] + r_thisframe->scale[i]*255};
	}
	
	/*
	** transform the min and max values into view space
	*/
	R_AliasTransformVector0( #{s_pmdl->mins}, #{RCF_transformed_min.p});
	R_AliasTransformVector0( #{s_pmdl->maxs}, #{RCF_transformed_max.p});
	
	if ( #{RCF_transformed_min.p[2]} >= ALIAS_Z_CLIP_PLANE )
		zfullyclipped = false;
	if ( #{RCF_transformed_max.p[2]} >= ALIAS_Z_CLIP_PLANE )
		zfullyclipped = false;
	
	if ( zfullyclipped )
	{
		return BBOX_TRIVIAL_REJECT;
	}
	if ( zclipped )
	{
		return ( BBOX_MUST_CLIP_XY | BBOX_MUST_CLIP_Z );
	}

	/*
	** build a transformed bounding box from the given min and max
	*/
	for ( i in 0...8 )
	{
		clipcode = 0;
		
		if ( (i & 1) != 0 )
			#{RCF_tmp.p[0] = s_pmdl->mins[0]};
		else
			#{RCF_tmp.p[0] = s_pmdl->maxs[0]};
		
		if ( (i & 2) != 0 )
			#{RCF_tmp.p[1] = s_pmdl->mins[1]};
		else
			#{RCF_tmp.p[1] = s_pmdl->maxs[1]};
		
		if ( (i & 4) != 0 )
			#{RCF_tmp.p[2] = s_pmdl->mins[2]};
		else
			#{RCF_tmp.p[2] = s_pmdl->maxs[2]};
		
		R_AliasTransformVector0_world( #{RCF_tmp.p}, #{RCF_transformed.p});
		
		for ( j in 0...4 )
		{
			dp = DotProduct( #{RCF_transformed.p}, #{view_clipplanes[j].normal.p} );
			
			if ( ( dp - #{view_clipplanes[j].dist} ) < 0.0 )
				clipcode |= 1 << j;
		}
		
		aggregate_and_clipcode &= clipcode;
		aggregate_or_clipcode  |= clipcode;
	}
	
	if ( aggregate_and_clipcode != 0 )
	{
		return BBOX_TRIVIAL_REJECT;
	}
	if ( aggregate_or_clipcode == 0 )
	{
		return BBOX_TRIVIAL_ACCEPT;
	}
	
	return BBOX_MUST_CLIP_XY;
}


/*
================
R_AliasSetUpTransform
================
*/
private function R_AliasSetUpTransform ()
{
	#{d| int				i}
	
// TODO: should really be stored with the entity instead of being reconstructed
// TODO: should use a look-up table
// TODO: could cache lazily, stored in the entity
// 
	AngleVectors( #{s_pmdl->angles}, #{s_alias_forward.p}, #{s_alias_right.p}, #{s_alias_up.p} );
	
// TODO: can do this with simple matrix rearrangement
	
	for (i in 0...3)
	{
		#{aliasworldtransform[i][0] =  s_alias_forward.p[i]};
		#{aliasworldtransform[i][1] = -s_alias_right.p[i]};
		#{aliasworldtransform[i][2] =  s_alias_up.p[i]};
	}
	
	#{aliasworldtransform[0][3] = s_pmdl->origin[0]-r_origin[0]};
	#{aliasworldtransform[1][3] = s_pmdl->origin[1]-r_origin[1]};
	#{aliasworldtransform[2][3] = s_pmdl->origin[2]-r_origin[2]};
	
// FIXME: can do more efficiently than full concatenation
	
// TODO: should be global, set when vright, etc., set
	VectorCopy (#{vright.p}, #{viewmatrix[0]});
	VectorCopy (#{vup.p}, #{viewmatrix[1]});
	VectorInverse (#{viewmatrix[1]});
	VectorCopy (#{vpn.p}, #{viewmatrix[2]});

	#{viewmatrix[0][3] = 0};
	#{viewmatrix[1][3] = 0};
	#{viewmatrix[2][3] = 0};

//	memcpy( aliasworldtransform, rotationmatrix, sizeof( aliastransform ) );

	R_ConcatTransforms0 ();//#{viewmatrix}, #{aliasworldtransform}, #{aliastransform});

	#{aliasworldtransform[0][3] = s_pmdl->origin[0]};
	#{aliasworldtransform[1][3] = s_pmdl->origin[1]};
	#{aliasworldtransform[2][3] = s_pmdl->origin[2]};
}

/*
================
R_ConcatTransforms
================
*/
private function R_ConcatTransforms0 ()
{
	
	#{aliastransform[0][0] = viewmatrix[0][0] * aliasworldtransform[0][0] + viewmatrix[0][1] * aliasworldtransform[1][0] +
				viewmatrix[0][2] * aliasworldtransform[2][0]};
	#{aliastransform[0][1] = viewmatrix[0][0] * aliasworldtransform[0][1] + viewmatrix[0][1] * aliasworldtransform[1][1] +
				viewmatrix[0][2] * aliasworldtransform[2][1]};
	#{aliastransform[0][2] = viewmatrix[0][0] * aliasworldtransform[0][2] + viewmatrix[0][1] * aliasworldtransform[1][2] +
				viewmatrix[0][2] * aliasworldtransform[2][2]};
	#{aliastransform[0][3] = viewmatrix[0][0] * aliasworldtransform[0][3] + viewmatrix[0][1] * aliasworldtransform[1][3] +
				viewmatrix[0][2] * aliasworldtransform[2][3] + viewmatrix[0][3]};
	#{aliastransform[1][0] = viewmatrix[1][0] * aliasworldtransform[0][0] + viewmatrix[1][1] * aliasworldtransform[1][0] +
				viewmatrix[1][2] * aliasworldtransform[2][0]};
	#{aliastransform[1][1] = viewmatrix[1][0] * aliasworldtransform[0][1] + viewmatrix[1][1] * aliasworldtransform[1][1] +
				viewmatrix[1][2] * aliasworldtransform[2][1]};
	#{aliastransform[1][2] = viewmatrix[1][0] * aliasworldtransform[0][2] + viewmatrix[1][1] * aliasworldtransform[1][2] +
				viewmatrix[1][2] * aliasworldtransform[2][2]};
	#{aliastransform[1][3] = viewmatrix[1][0] * aliasworldtransform[0][3] + viewmatrix[1][1] * aliasworldtransform[1][3] +
				viewmatrix[1][2] * aliasworldtransform[2][3] + viewmatrix[1][3]};
	#{aliastransform[2][0] = viewmatrix[2][0] * aliasworldtransform[0][0] + viewmatrix[2][1] * aliasworldtransform[1][0] +
				viewmatrix[2][2] * aliasworldtransform[2][0]};
	#{aliastransform[2][1] = viewmatrix[2][0] * aliasworldtransform[0][1] + viewmatrix[2][1] * aliasworldtransform[1][1] +
				viewmatrix[2][2] * aliasworldtransform[2][1]};
	#{aliastransform[2][2] = viewmatrix[2][0] * aliasworldtransform[0][2] + viewmatrix[2][1] * aliasworldtransform[1][2] +
				viewmatrix[2][2] * aliasworldtransform[2][2]};
	#{aliastransform[2][3] = viewmatrix[2][0] * aliasworldtransform[0][3] + viewmatrix[2][1] * aliasworldtransform[1][3] +
				viewmatrix[2][2] * aliasworldtransform[2][3] + viewmatrix[2][3]};
}

/*
================
R_AliasTransformVector
================
*/
private function R_AliasTransformVector0( vin : Int, out : Int )
{
	Memory.setFloat(out,	DotProduct(vin, #{aliastransform[0]}) + #{aliastransform[0][3]});
	Memory.setFloat(out+4,	DotProduct(vin, #{aliastransform[1]}) + #{aliastransform[1][3]});
	Memory.setFloat(out+8,	DotProduct(vin, #{aliastransform[2]}) + #{aliastransform[2][3]});
}

/*
================
R_AliasTransformVector
================
*/
private function R_AliasTransformVector0_world( vin : Int, out : Int )
{
	Memory.setFloat(out,	DotProduct(vin, #{aliasworldtransform[0]}) + #{aliasworldtransform[0][3]});
	Memory.setFloat(out+4,	DotProduct(vin, #{aliasworldtransform[1]}) + #{aliasworldtransform[1][3]});
	Memory.setFloat(out+8,	DotProduct(vin, #{aliasworldtransform[2]}) + #{aliasworldtransform[2][3]});
}
